<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<script type="text/javascript">
			//使用Object原生构造函数或对象字面量都可以用来创建对象，但缺点是：使用同一个接口创建很多对象，会产生大量重复的代码
			
			//1.工厂模式
			//工厂模式虽然解决了创建多个相似对象的问题，但却没有解决对象识别的问题（即怎样知道一个对象的类型）。
					    function createPerson(name,age,job){
							var o = new Object();
							o.name = name;
							o.age = age;
							o.job = job;
							o.sayName = function(){
								alert(this.name);
							}
							return o
						}
						var person1 = createPerson("Nicholas", 29, "Software Engineer");
						var person2 = createPerson("Greg", 27, "Doctor");
			
			//2.构造函数模式
			     //ECMAScript 中的构造函数可用来创建特定类型的对象
			     //像 Object 和 Array 这样的原生构造函数，在运行时会自动出现在执行环境中
			     //也可以创建自定义的构造函数，从而定义自定义对象类型的属性和方法
			     
			     //使用 new 操作符调用构造函数实际上会经历以下 4个步骤：
						//(1) 创建一个新对象；
						//(2) 将构造函数的作用域赋给新对象（因此 this 就指向了这个新对象）；
						//(3) 执行构造函数中的代码（为这个新对象添加属性）；
						//(4) 返回新对象
						
				 //构造函数与其他函数的唯一区别，就在于调用它们的方式不同
				 //构造函数毕竟也是函数，不存在定义构造函数的特殊语法
				 //任何函数，只要通过new操作符来调用，那它就可以作构造函数
					     function Person(name,age,job){
					      	 this.name = name;
					      	 this.age = age;
					      	 this.job = job;
					      	 this.sayName = function(){
					      	 	console.log(this.name);
					      	 };
					     }
					     var person1 = new Person("Nicholas", 29, "Software Engineer");
					     var person2 = new Person("Greg", 27, "Doctor");
					     console.log(person1,person2);
					     console.log(person1.constructor==Person);//true
					     console.log(person2.constructor==Person);//true
			     //创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型
			     //构造函数的问题
			      		//每个方法都要在每个实例上重新创建一遍
				   		//不同实例上的同名函数是不相等的
			              console.log(person1.sayName == person2.sayName);//false
		     
		     //3.原型模式
		         //我们创建的每个函数都有一个 prototype（原型）属性，该属性（是一个指针）指向函数的原型对象,
		         //prototype就是通过调用构造函数而创建的那个对象实例的原型对象
		         //使用原型对象的好处：可以让所有对象实例共享它包含的属性和方法
		         //所有原型对象都会自动获得一个 constructor（构造函数）属性，这个属性包含一个指向 prototype 属性所在函数的指针
						         function Person(){
						         	
						         }
						         console.log(Person.prototype.constructor);
						         Person.prototype.name = "ls";
						         Person.prototype.age = 24;
						         Person.prototype.job ="前端";
						         Person.prototype.sayName = function(){
						         	console.log(this.name);
						         };
						         var person1 = new Person();
						         person1.sayName();//"ls"
						         var person2 = new Person();
						         person2.sayName();//"ls"
						         console.log(person1.sayName == person2.sayName);//true
		         
		       //3.1  理解原型对象
		           //原型对象的用途是包含可以特定类型的所有实例共享的属性和方法
		           //只要创建了一个新函数，就会根据特定的规则为该函数创建一个prototype属性，该属性指向函数的原型对象
		           //默认情况下，所有的原型对象都会自动获得一个constructor（构造函数）属性
		           //原型对象的constructor属性包含一个指向prototype属性所在函数的指针
		           //Person.prototype.constructor指向了Person
		           
		           
		           //创建自定义的构造函数之后，其原型对象默认只会取得constructor属性，其他方法则都是从Object继承而来的
                   //当调用构造函数创建一个新实例后，该实例的内部将包含一个指针（内部属性），指向构造函数的原型对象。
                   //Firefox、Safari 和 Chrome 在每个对象上都支持一个属性__proto__
                   //这个连接存在于实例和构造函数的原型对象之间，不是存在于实例和构造函数之间
                   
                   
                   //Person.prototype 指向了Person函数的原型对象
                   //Person.prototype.constructor 原型对象的constructor属性又指回了 Person函数
                   //Person 的每个实例————person1 和 person2 都包含一个内部属性，该属性仅仅指向了Person函数的原型对象 Person.prototype
                         console.log(person1.__proto__ === Person.prototype);//true
                         console.log(Person.prototype.isPrototypeOf(person1));//true
                         console.log(person1);
		         		 console.log(Object.getPrototypeOf(person1) == Person.prototype);//true

                  /*每当代码读取某个对象的某个属性时，都会执行一次搜索，目标是具有给定名字的属性。
                   *搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性，则返回该属性。
                   *如果没有找到，则继续搜索指针指向的原型对象，在原型对象中查找具有给定名字的属性。
                   *如果在原型对象中找到了这个属性，则返回该属性的值。*/
                     
                  //原型最初只包含 constructor 属性，而该属性也是共享的，因此可以通过对象实例访问
                  //可以通过对象实例访问保存在原型中的值，但不能通过对象实例重写原型中的值，因为搜索属性时会从实例本身开始，这样就会屏蔽原型对象中的值
                  //当为对象实例添加一个属性时，这个属性就会屏蔽原型对象中保存的同名属性;
                  //换句话说，添加这个属性只会阻止我们访问原型中的那个属性，但不会修改那个属性。
                          function Person(){
                          	
                          }
                          Person.prototype.name = "ls";
                          Person.prototype.age = 24;
                          Person.prototype.job = "前端工程师"
                          Person.prototype.sayName = function(){
                          	  console.log(this.name);
                          }
                          var person1 = new Person();
                          var person2 = new Person();
                          person1.name = "xxx";//为person1实例添加name属性，当访问person1.name时会屏蔽原型对象中保存的同名属性
                          console.log(person1);
                          console.log(person1.name);//xxx  来自实例
                          console.log(person2.name);//ls   来自原型
                          
                  //用 hasOwnProperty()方法可以检测一个属性是存在于实例中，还是存在于原型中
                  //这个方法（不要忘了它是从 Object 继承来的）只在给定属性存在于对象实例中时，才会返回 true。
                          function Person(){
                          	
                          }
                          Person.prototype.name = "ls";
                          Person.prototype.age = 24;
                          Person.prototype.job = "前端工程师"
                          Person.prototype.sayName = function(){
                          	  console.log(this.name);
                          }
                          var person1 = new Person();
                          var person2 = new Person();
                          console.log(person1.hasOwnProperty("name"));//false
                          person1.name = "xxx";
                          console.log(person1.name);//xxx 来自实例
                          console.log(person1.hasOwnProperty("name"));//true
                          console.log(person2.name);//ls 来自原型
                          console.log(person2.hasOwnProperty("name"));//false
                          delete person1.name;//删除实例属性
                          console.log(person1.name);//ls 来自原型
                          console.log(person1.hasOwnProperty("name"));//false
                          
                    //3.2 原型与in操作符
                      //有两种方式使用 in 操作符：单独使用和在 for-in 循环中使用。
                      //在单独使用时，in 操作符会在通过对象能够访问给定属性时返回 true，无论该属性存在于实例中还是原型中。
	                          function Person(){
	                          	
	                          }
	                          Person.prototype.name = "ls";
	                          Person.prototype.age = 24;
	                          Person.prototype.job = "前端工程师"
	                          Person.prototype.sayName = function(){
	                          	  console.log(this.name);
	                          }
	                          var person1 = new Person();
	                          var person2 = new Person();
	                          console.log(person1.hasOwnProperty("name"));//false
	                          console.log("name" in person1);//true
	                          
	                          person1.name = "xxx";
	                          console.log(person1.name);//xxx 来自实例
	                          console.log(person1.hasOwnProperty("name"));//true
	                          console.log("name" in person1);//true
	                          
	                          console.log(person2.name);//ls 来自原型
	                          console.log(person2.hasOwnProperty("name"));//false
	                          console.log("name" in person2);//true
	                          
	                          delete person1.name;//删除实例属性
	                          console.log(person1.name);//ls 来自原型
	                          console.log(person1.hasOwnProperty("name"));//false
	                          console.log("name" in person1);//true
	                          /*在上述例子中name 属性要么是直接在对象上访问到的，要么是通过原型访问到的。
	                           *因此，调用"name" in person1 始终都返回 true，无论该属性存在于实例中还是存在于原型中*/
	                          
	                  //同时使用 hasOwnProperty()方法和 in 操作符，就可以确定该属性到底是存在于实例中，还是存在于原型中
		                      function hasPrototypeProperty(object,name){
		                       	  return !object.hasOwnProperty(name)&&(name in object);
		                      }
	                          function Person(){
	                          	
	                          }
	                          Person.prototype.name = "ls";
	                          Person.prototype.age = 24;
	                          Person.prototype.job = "前端工程师"
	                          Person.prototype.sayName = function(){
	                          	  console.log(this.name);
	                          }
	                          var person = new Person();
	                          console.log(hasPrototypeProperty(person,"name"));//true
	                          person.name = "xxx";
	                          console.log(hasPrototypeProperty(person,"name"));//false

                     //要取得对象上所有可枚举的实例属性，可以使用 ECMAScript 5 的 Object.keys()方法。
                     //这个方法接收一个对象作为参数，返回一个包含所有可枚举（特性enumerable为true）属性的字符串数组
                             function Person(){
                             	
                             }
                             Person.prototype.name = "ls";
                             Person.prototype.age = 24;
                             Person.prototype.job = "前端工程师";
                             Person.prototype.sayName = function(){
                             	console.log(this.sayName);
                             }
                             var keys = Object.keys(Person.prototype);
                             console.log(keys);//name age job sayName
                             var p1 = new Person();
                             p1.name = "xxx";
                             p1.age = 25;
                             var p1keys = Object.keys(p1);
                             console.log(p1keys);//name age
                             
                     
                    //3.3 更简单的原型语法
                          function Person(){
                          	
                          }
                          Person.prototype = {
                          	 name:"ls",
                          	 age:24,
                          	 job:"前端工程师",
                          	 sayName:function(){
                          	 	console.log(this.name)
                          	 }
                          };
                          var friend = new Person();
                          //上述例子将Person.prototype设置为等于一个以对象字面量形式创建的新对象
                          //constructor 属性也就变成了新对象的 constructor 属性（指向 Object 构造函数），不再指向Person函数
                          console.log(friend instanceof Object);//true
                          console.log(friend instanceof Person);//true
                          console.log(friend.constructor==Person);//fasle
                          console.log(friend.constructor==Object);//true
                          
                    //3.4 原型的动态性
                         //由于在原型中查找值的过程是一次搜索
                         //因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。
	                         	function Person(){
	                         	
								} 
								var friend = new Person();
								Person.prototype.sayHi = function(){
								   console.log("hi");
								}
								friend.sayHi();//"hi" 没有问题！
                        //重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系；之前存在的实例对象引用的仍然是最初的原型。
                                function Person(){
                                	
                                }
                                var friend = new Person();
                                Person.prototype = {
                                	constructor:Person,
                                	name:"ls",
                                	sayName:function(){
                                		console.log(this.name);
                                	}
                                }
                                friend.sayName();//报错

                     //3.5 原生对象的原型
                           //所有原生引用类型（Object、Array、String，等等）都在其构造函数的原型上定义了方法。
                                   console.log(typeof Array.prototype.sort);//function
								   console.log(typeof String.prototype.substring);//function
                     //3.6 原型对象的问题
                          //所有实例在默认情况下都将取得相同的属性值
                          /*原型中所有属性是被很多实例共享的，这种共享对于函数非常合适。对于那些包含基本值的属性倒
							也说得过去，毕竟（如前面的例子所示），通过在实例上添加一个同名属性，可以隐藏原型中的对应属
							性。然而，对于包含引用类型值的属性来说，问题就比较突出了*/   
						            Person.prototype = {
										 construct:Person,
										 name:"ls",
										 age:24,
										 friends:["xxx","sss"]
									}
								    var person1 = new Person();
									var person2 = new Person();
									person1.age = 20;
									console.log(person1.age);//20
									console.log(person2.age)//24
									person1.friends.push("jjj");
									console.log(person1);
									console.log(person1.friends);//["xxx","sss","lll"] 导致所有实例共享一个数组
									console.log(person2.friends);//["xxx","sss","lll"]
                                   
                           
                //4.组合使用构造函数模式和原型模式
			                function Person(name,age,job){
			                	this.name = name;
			                	this.age = age;
			                	this.job = job;
			                	this.friends = ["Shelby", "Court"]; 
			                }
			                Person.prototype = {
			                    constructor:Person,
			                    sayName:function(){
			                    	console.log(this.name);
			                    }
			                }
			                var person1 = new Person("Nicholas", 29, "Software Engineer");
			                var person2 = new Person("Greg", 27, "Doctor");
			                person1.friends.push("Van");
			                console.log(person1.friends);//"Shelby,Count,Van" 
			                console.log(person2.friends);//"Shelby,Count"
			                console.log(person1.name===person2.name);//false
			                console.log(person1.friends===person2.friends);//false
			                console.log(person1.sayName===person2.sayName);//true
                
                //5.动态原型模式
                //  使用动态原型模式时，不能使用对象字面量重写原型。
                //  如果在已经创建了实例的情况下重写原型，那么就会切断已存在实例与新原型之间的联系
		                    function Person(name,age,job){
								  //属性
									this.name = name;
									this.age = age;
									this.job = job;
									//方法
									if(typeof this.sayName != "function"){
										 Person.prototype.sayName = function(){
										 	  console.log(this.name);
										 }
									}
							}
							
							var friend = new Person("ls",24,"前端工程师");
							console.log(friend);
							friend.sayName();//ls
                  
                  //6.寄生构造函数模式
                    //关于寄生构造函数模式，有一点需要说明：首先，返回的对象与构造函数或者与构造函数的原型属性之间没有关系；也就是说，构造函数返回的对象与在构造函数外部创建的对象没有什么不同。
	                        function Person(name,age,job){
								var o = new Object();
								o.name = name;
								o.age = age;
								o.job = job;
								o.sayName = function(){
									console.log(this.name);
								}
								return o;
							}
							var firend = new Person('ls',24,'前端工程师');
                  //7.稳妥构造函数模式
                      //稳妥对象，指的是没有公共属性，而且其方法也不引用 this 的对象。
                      //稳妥构造函数遵循与寄生构造函数类似的模式，但有两点不同：一是新创建对象的实例方法不引用 this；二是不使用 new 操作符调用构造函数。
                      //与寄生构造函数模式类似，使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系
	                        function Person(name,age,job){
								var o = new Object();
								o.sayName = function(){
									console.log(name);
								}
								return o;
							}
							var firend = Person('ls',24,'前端工程师');
		</script> 
	</body>
</html>
